"""
全量代码，需要替换主入口的Excel文件路径以及测试目标URL地址
"""
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import logging
import datetime
import os
import time
import colorama
import platform
import json


class WebUIAutomation:
    def __init__(self, excel_path):
        """初始化WebUI自动化测试类"""
        try:
            # 确保在使用colorama之前初始化
            colorama.init()

            self.driver = webdriver.Firefox()
            self.test_data = pd.read_excel(excel_path)
            self.base_url = None

            # 创建基础目录结构
            self.create_base_directories()

            # 设置日志记录
            self.setup_logging()

            # 创建本次测试的截图目录
            timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
            self.screenshot_dir = os.path.join('../screenshots', timestamp)
            if not os.path.exists(self.screenshot_dir):
                os.makedirs(self.screenshot_dir)
                self.logger.info(f"创建截图目录: {self.screenshot_dir}")

            self.test_results = []
            self.test_start_time = None
            self.test_end_time = None
            self.browser_info = self.driver.capabilities
            self.platform_info = {
                'os': platform.system(),
                'os_version': platform.version(),
                'python_version': platform.python_version()
            }

        except FileNotFoundError:
            raise FileNotFoundError(f"找不到Excel文件: {excel_path}")
        except Exception as e:
            raise Exception(f"初始化失败: {str(e)}")

    def create_base_directories(self):
        """创建基础目录结构"""
        # 创建截图目录
        if not os.path.exists('../screenshots'):
            os.makedirs('../screenshots')

        # 创建测试报告目录
        if not os.path.exists('../testReport'):
            os.makedirs('../testReport')

        # 创建日志目录
        if not os.path.exists('../logs'):  # 修改为当前目录下的 logs
            os.makedirs('../logs')

    def setup_logging(self):
        """设置日志记录"""
        # 定义颜色代码
        GREEN = colorama.Fore.GREEN
        RED = colorama.Fore.RED
        ORANGE = colorama.Fore.YELLOW
        RESET = colorama.Fore.RESET

        # 使用当前目录下的 logs 文件夹
        log_dir = '../logs'
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)

        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        log_file = os.path.join(log_dir, f'test_{timestamp}.log')

        class ColoredFormatter(logging.Formatter):
            def format(self, record):
                if record.levelno == logging.ERROR:
                    record.msg = f"{RED}{record.msg}{RESET}"
                elif "成功" in str(record.msg) or "Pass" in str(record.msg):
                    record.msg = f"{GREEN}{record.msg}{RESET}"
                else:
                    record.msg = f"{ORANGE}{record.msg}{RESET}"
                return super().format(record)

        # 创建格式化器
        formatter = ColoredFormatter('%(asctime)s - %(levelname)s - %(message)s')

        # 设置文件处理器
        file_handler = logging.FileHandler(log_file, encoding='utf-8')
        file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))  # 文件中不使用颜色

        # 设置控制台处理器
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(formatter)

        # 配置日志记录器
        logger = logging.getLogger(__name__)
        logger.setLevel(logging.INFO)

        # 清除可能存在的旧处理器
        logger.handlers.clear()

        logger.addHandler(file_handler)
        logger.addHandler(console_handler)

        self.logger = logger
        self.logger.info("日志系统初始化完成")

    def take_screenshot(self, test_case):
        """截图功能"""
        try:
            timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
            # 生成文件名
            filename = f"{test_case['测试用例编号']}_{test_case['步骤编号']}_{timestamp}.png"
            # 完整的保存路径
            save_path = os.path.join(self.screenshot_dir, filename)

            # 保存截图
            self.driver.save_screenshot(save_path)

            # 返回相对于测试报告的路径（使用 ../）
            relative_path = f"../screenshots/{os.path.basename(self.screenshot_dir)}/{filename}"
            # self.logger.info(f"截图已保存: {save_path}")
            return relative_path

        except Exception as e:
            self.logger.error(f"截图失败: {str(e)}")
            return None

    def execute_test_step(self, test_case):
        start_time = datetime.datetime.now()
        try:
            self.logger.info(f"开始执行测试用例 {test_case['测试用例编号']} 步骤 {test_case['步骤编号']}")

            # 对于等待操作，不需要先获取元素
            if test_case['测试步骤关键字'] == '等待':
                result = self.execute_keyword(test_case, None)
            else:
                # 其他操作需要先获取元素
                element = self.wait_for_element_presence(
                    test_case['定位方式'],
                    test_case['定位地址']
                )
                result = self.execute_keyword(test_case, element)

            # 截图
            screenshot = self.take_screenshot(test_case)

            end_time = datetime.datetime.now()
            duration = end_time - start_time

            # 记录测试结果
            self.test_results.append({
                '测试用例编号': test_case['测试用例编号'],
                '步骤编号': test_case['步骤编号'],
                '测试步骤': test_case['测试步骤'],
                '执行结果': 'Pass',
                '错误信息': '',
                '截图': screenshot,
                '开始时间': start_time,
                '结束时间': end_time,
                '执行时长': duration
            })

            self.logger.info(f"✓ 测试用例 {test_case['测试用例编号']} 步骤 {test_case['步骤编号']} 执行成功")

        except Exception as e:
            end_time = datetime.datetime.now()
            duration = end_time - start_time
            error_msg = str(e)
            self.logger.error(f"✗ 测试用例 {test_case['测试用例编号']} 步骤 {test_case['步骤编号']} 执行失败: {error_msg}")
            screenshot = self.take_screenshot(test_case)

            self.test_results.append({
                '测试用例编号': test_case['测试用例编号'],
                '步骤编号': test_case['步骤编号'],
                '测试步骤': test_case['测试步骤'],
                '执行结果': 'Fail',
                '错误信息': error_msg,
                '截图': screenshot,
                '开始时间': start_time,
                '结束时间': end_time,
                '执行时长': duration
            })

    def execute_keyword(self, test_case, element):
        """执行关键字操作"""
        keyword = test_case['测试步骤关键字']
        value = test_case['参数值']
        expected_result = test_case.get('预期结果', '')

        keyword_actions = {
            '点击': lambda: element.click(),
            '输入': lambda: element.send_keys(value),
            '清除': lambda: element.clear(),
            '双击': lambda: ActionChains(self.driver).double_click(element).perform(),
            '右击': lambda: ActionChains(self.driver).context_click(element).perform(),
            '悬停': lambda: ActionChains(self.driver).move_to_element(element).perform(),
            '获取文本': lambda: element.text,
            '获取属性': lambda: element.get_attribute(value),
            '按键': lambda: element.send_keys(getattr(Keys, value)),
            '提交': lambda: element.submit(),
            '判断是否显示': lambda: element.is_displayed(),
            '判断是否启用': lambda: element.is_enabled(),
            '判断是否选中': lambda: element.is_selected(),
            '切换框架': lambda: self.driver.switch_to.frame(element),
            '返回默认框架': lambda: self.driver.switch_to.default_content(),
            '切换窗口': lambda: self.switch_window(value),
            '执行JS': lambda: self.driver.execute_script(value, element),
            '等待': lambda: self.wait_sleep(value),  # 强制等待
            '等待元素可见': lambda: self.wait_for_element(test_case['定位方式'], test_case['定位地址'], float(value or 10)),
            '等待元素可点击': lambda: self.wait_for_element_clickable(test_case['定位方式'], test_case['定位地址'],
                                                               float(value or 10)),
            '等待元素存在': lambda: self.wait_for_element_presence(test_case['定位方式'], test_case['定位地址'], float(value or 10)),
            '等待文本出现': lambda: self.wait_for_text_present(test_case['定位方式'], test_case['定位地址'], expected_result,
                                                         float(value or 10)),
        }

        if keyword in keyword_actions:
            return keyword_actions[keyword]()
        else:
            raise ValueError(f"不支持的关键字操作: {keyword}")

    def switch_window(self, window_index):
        """切换窗口"""
        handles = self.driver.window_handles
        if window_index == '最新':
            self.driver.switch_to.window(handles[-1])
        elif window_index.isdigit() and int(window_index) < len(handles):
            self.driver.switch_to.window(handles[int(window_index)])

    def generate_report(self):
        """生成HTML测试报告"""
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        report_file = f'testReport/test_report_{timestamp}.html'

        # 统计测试结果
        total_cases = len(set(result['测试用例编号'] for result in self.test_results))
        passed_cases = len(set(result['测试用例编号'] for result in self.test_results if result['执行结果'] == 'Pass'))
        failed_cases = total_cases - passed_cases
        pass_rate = (passed_cases / total_cases * 100) if total_cases > 0 else 0

        # 计算总执行时间
        if self.test_results:
            first_step = min(self.test_results, key=lambda x: x['开始时间'])
            last_step = max(self.test_results, key=lambda x: x['结束时间'])
            total_duration = last_step['结束时间'] - first_step['开始时间']
        else:
            total_duration = datetime.timedelta(0)

        # 生成图表数据
        step_labels = self.get_step_labels()
        step_durations = self.get_step_durations()

        html_content = f'''
        <!DOCTYPE html>
        <html>
        <head>
            <title>Web UI自动化测试报告</title>
            <meta charset="utf-8">
            <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
            <style>
                body {{
                    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                    margin: 0;
                    padding: 0;
                    background-color: #f5f5f5;
                    color: #333;
                }}
                
                .container {{
                    max-width: 1200px;
                    margin: 0 auto;
                    padding: 20px;
                }}
                
                .header {{
                    text-align: center;
                    padding: 30px;
                    background: linear-gradient(135deg, #6e8efb, #a777e3);
                    color: white;
                    border-radius: 10px;
                    margin-bottom: 30px;
                    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
                }}
                
                .header h1 {{
                    margin: 0;
                    font-size: 2.5em;
                    font-weight: 300;
                }}
                
                .header p {{
                    margin: 10px 0 0;
                    opacity: 0.9;
                }}
                
                .env-info {{
                    background: white;
                    padding: 25px;
                    border-radius: 10px;
                    margin: 20px 0;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                }}
                
                .env-info h2 {{
                    color: #2c3e50;
                    margin-top: 0;
                    border-bottom: 2px solid #eee;
                    padding-bottom: 10px;
                }}
                
                .test-metrics {{
                    display: grid;
                    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
                    gap: 20px;
                    margin: 20px 0;
                }}
                
                .metric-card {{
                    background: white;
                    padding: 20px;
                    border-radius: 10px;
                    text-align: center;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                    transition: transform 0.2s;
                }}
                
                .metric-card:hover {{
                    transform: translateY(-5px);
                }}
                
                .metric-card h3 {{
                    color: #2c3e50;
                    margin-top: 0;
                }}
                
                .metric-card p {{
                    font-size: 1.5em;
                    color: #3498db;
                    margin: 10px 0;
                }}
                
                .chart-container {{
                    background: white;
                    padding: 20px;
                    border-radius: 10px;
                    margin: 20px 0;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                }}
                
                .failure-summary {{
                    background: #fff3f3;
                    padding: 20px;
                    border-radius: 10px;
                    margin: 20px 0;
                    border-left: 4px solid #dc3545;
                }}
                
                .failure-summary h2 {{
                    color: #dc3545;
                    margin-top: 0;
                }}
                
                .failure-summary ul {{
                    list-style-type: none;
                    padding: 0;
                }}
                
                .failure-summary li {{
                    margin: 10px 0;
                    padding: 15px;
                    background: rgba(220, 53, 69, 0.1);
                    border-radius: 5px;
                }}
                
                .test-case {{
                    background: white;
                    padding: 20px;
                    border-radius: 10px;
                    margin: 20px 0;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                }}
                
                .test-step {{
                    border-left: 3px solid #3498db;
                    margin: 15px 0;
                    padding: 10px 20px;
                }}
                
                .status-badge {{
                    display: inline-block;
                    padding: 5px 15px;
                    border-radius: 15px;
                    color: white;
                    font-weight: bold;
                    margin: 5px 0;
                }}
                
                .status-pass {{
                    background-color: #2ecc71;
                }}
                
                .status-fail {{
                    background-color: #e74c3c;
                }}
                
                .step-timing {{
                    background-color: #f8f9fa;
                    padding: 15px;
                    border-radius: 8px;
                    margin: 10px 0;
                    font-family: monospace;
                    font-size: 0.9em;
                }}
                
                .step-timing p {{
                    margin: 5px 0;
                    color: #666;
                }}
                
                .screenshot {{
                    max-width: 300px;
                    height: auto;
                    border-radius: 8px;
                    margin: 10px 0;
                    border: 1px solid #ddd;
                    cursor: pointer;
                    transition: transform 0.2s;
                }}
                
                .screenshot:hover {{
                    transform: scale(1.05);
                }}
                
                .modal {{
                    display: none;
                    position: fixed;
                    z-index: 1000;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background-color: rgba(0,0,0,0.9);
                }}
                
                .modal-content {{
                    margin: auto;
                    display: block;
                    max-width: 90%;
                    max-height: 90%;
                    margin-top: 2%;
                }}
                
                .close {{
                    position: absolute;
                    right: 35px;
                    top: 15px;
                    color: #f1f1f1;
                    font-size: 40px;
                    font-weight: bold;
                    cursor: pointer;
                }}
                
                @media (max-width: 768px) {{
                    .container {{
                        padding: 10px;
                    }}
                    
                    .test-metrics {{
                        grid-template-columns: 1fr;
                    }}
                }}
                
                .charts-row {{
                    display: grid;
                    grid-template-columns: 1fr 2fr;  /* 饼图占1份宽度，折线图占2份宽度 */
                    gap: 20px;
                    margin: 20px 0;
                }}
                
                .chart-container {{
                    background: white;
                    padding: 20px;
                    border-radius: 10px;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                    height: 300px;  /* 固定高度 */
                    position: relative;
                }}

                /* 为饼图容器添加特殊样式 */
                .pie-chart-container {{
                    max-width: 400px;  /* 限制饼图容器最大宽度 */
                    margin: 0 auto;
                }}
            </style>
        </head>
        <body>
            <div class="container">
                <div class="header">
                    <h1>Web UI自动化测试报告</h1>
                    <p>测试执行时间: {self.test_start_time.strftime('%Y-%m-%d %H:%M:%S')}</p>
                </div>

                <div class="env-info">
                    <h2>测试环境信息</h2>
                    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
                        <div>
                            <strong>浏览器：</strong>
                            <p>{self.browser_info.get('browserName', 'Unknown')} {self.browser_info.get('browserVersion', '')}</p>
                        </div>
                        <div>
                            <strong>操作系统：</strong>
                            <p>{self.platform_info['os']} {self.platform_info['os_version']}</p>
                        </div>
                        <div>
                            <strong>Python版本：</strong>
                            <p>{self.platform_info['python_version']}</p>
                        </div>
                        <div>
                            <strong>测试脚本：</strong>
                            <p>{os.path.basename(__file__)}</p>
                        </div>
                    </div>
                </div>

                <div class="test-metrics">
                    <div class="metric-card">
                        <h3>总执行时间</h3>
                        <p>{total_duration.total_seconds():.2f} 秒</p>
                    </div>
                    <div class="metric-card">
                        <h3>测试用例总数</h3>
                        <p>{total_cases}</p>
                    </div>
                    <div class="metric-card">
                        <h3>通过率</h3>
                        <p>{pass_rate:.1f}%</p>
                    </div>
                </div>

                <div class="charts-row">
                    <div class="chart-container pie-chart-container">
                        <canvas id="resultPieChart"></canvas>
                    </div>
                    <div class="chart-container">
                        <canvas id="stepTimeTrend"></canvas>
                    </div>
                </div>

                <!-- 测试用例详细信息 -->
                <div class="test-cases">
                    <h2>测试用例详细信息</h2>
    '''

        # 添加测试步骤详细信息
        current_case = None
        for step in self.test_results:
            if current_case != step['测试用例编号']:
                if current_case is not None:
                    html_content += '</div>'
                current_case = step['测试用例编号']
                html_content += f'''
                    <div class="test-case">
                        <h3>测试用例: {current_case}</h3>
                '''

            html_content += f'''
                <div class="test-step">
                    <h4>步骤 {step['步骤编号']}: {step['测试步骤']}</h4>
                    <span class="status-badge status-{step['执行结果'].lower()}">{step['执行结果']}</span>
                    <div class="step-timing">
                        <p>开始时间: {step['开始时间'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}</p>
                        <p>结束时间: {step['结束时间'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}</p>
                        <p>执行时长: {step['执行时长'].total_seconds():.3f} 秒</p>
                    </div>
                '''

            if step.get('错误信息'):
                html_content += f'''
                    <div class="error-message">
                        <p>错误信息: {step['错误信息']}</p>
                    </div>
                '''

            if step.get('截图'):
                html_content += f'''
                    <div class="screenshot-container">
                        <img class="screenshot" src="{step['截图']}" alt="测试截图" onclick="showImage(this.src)">
                    </div>
                '''

            html_content += '</div>'

        if current_case is not None:
            html_content += '</div>'

        # 添加图表脚本和模态框
        html_content += f'''
                    </div>  <!-- 关闭 test-cases -->
                    
                    <!-- 图片模态框 -->
                    <div id="imageModal" class="modal">
                        <span class="close" onclick="closeModal()">&times;</span>
                        <img class="modal-content" id="modalImage">
                    </div>

                    <script>
                        // 结果饼图
                        new Chart(document.getElementById('resultPieChart'), {{
                            type: 'pie',
                            data: {{
                                labels: ['通过', '失败'],
                                datasets: [{{
                                    data: [{passed_cases}, {failed_cases}],
                                    backgroundColor: ['#2ecc71', '#e74c3c'],
                                    borderWidth: 1
                                }}]
                            }},
                            options: {{
                                responsive: true,
                                maintainAspectRatio: true,
                                plugins: {{
                                    title: {{
                                        display: true,
                                        text: '测试执行结果分布'
                                    }},
                                    legend: {{
                                        position: 'bottom'
                                    }}
                                }},
                                layout: {{
                                    padding: {{
                                        left: 10,
                                        right: 10,
                                        top: 10,
                                        bottom: 10
                                    }}
                                }}
                            }}
                        }});

                        // 步骤执行时间趋势图
                        new Chart(document.getElementById('stepTimeTrend'), {{
                            type: 'line',
                            data: {{
                                labels: {step_labels},
                                datasets: [{{
                                    label: '步骤执行时间（秒）',
                                    data: {step_durations},
                                    borderColor: 'rgb(75, 192, 192)',
                                    tension: 0.1
                                }}]
                            }},
                            options: {{
                                responsive: true,
                                maintainAspectRatio: true,
                                plugins: {{
                                    title: {{
                                        display: true,
                                        text: '步骤执行时间趋势'
                                    }}
                                }},
                                scales: {{
                                    y: {{
                                        beginAtZero: true,
                                        title: {{
                                            display: true,
                                            text: '执行时间（秒）'
                                        }}
                                    }}
                                }}
                            }}
                        }});

                        // 图片模态框相关函数
                        function showImage(src) {{
                            var modal = document.getElementById("imageModal");
                            var modalImg = document.getElementById("modalImage");
                            modal.style.display = "block";
                            modalImg.src = src;
                        }}
                        
                        function closeModal() {{
                            document.getElementById("imageModal").style.display = "none";
                        }}
                        
                        window.onclick = function(event) {{
                            var modal = document.getElementById("imageModal");
                            if (event.target == modal) {{
                                modal.style.display = "none";
                            }}
                        }}
                    </script>
                </div>  <!-- 关闭 container -->
            </body>
            </html>
        '''

        # 写入文件
        with open(report_file, 'w', encoding='utf-8') as f:
            f.write(html_content)

        self.logger.info(f"测试报告已生成: {report_file}")

    def run_tests(self):
        """运行所有测试用例"""
        if not self.base_url:
            raise ValueError("请先使用set_base_url()设置基础URL")

        self.logger.info("开始执行测试套件")
        self.driver.get(self.base_url)

        # 打印Excel列名，用于调试
        # self.logger.info(f"Excel文件包含的列: {list(self.test_data.columns)}")

        self.test_start_time = datetime.datetime.now()

        for index, row in self.test_data.iterrows():
            test_case = {}
            # 用更灵活的列名匹配
            column_mappings = {
                '测试用例编号': ['测试用例编号', 'case_id', 'test_case_id'],
                '测试点': ['测试点', 'test_point'],
                '步骤编号': ['步骤编号', 'step_id', 'step_no'],
                '测试步骤': ['测试步骤', 'test_step'],
                '是否执行': ['是否执行', 'execute', 'run'],
                '测试步骤关键字': ['测试步骤关键字', 'keyword'],
                '定位方式': ['定位方式', 'locate_type', 'by'],
                '定位地址': ['定位地址', 'locate_value', 'value'],
                '参数值': ['参数值', 'parameter', 'param'],
                '预期结果': ['预期结果', 'expected_result', 'expected']
            }

            for key, possible_names in column_mappings.items():
                value = None
                for name in possible_names:
                    if name in row.index:
                        value = row[name]
                        break
                test_case[key] = str(value) if pd.notna(value) else ''

            # 检查必要的字段
            if not test_case['测试用例编号'] or not test_case['步骤编号']:
                self.logger.warning(f"跳过无效的测试用例行: {index + 1}")
                continue

            if test_case['是否执行'].upper() in ['Y', 'YES', '是', '1', 'TRUE', '']:
                self.execute_test_step(test_case)

        self.test_end_time = datetime.datetime.now()

        self.generate_report()
        self.logger.info("测试套件执行完成")

    def close(self):
        """关闭浏览器"""
        self.driver.quit()
        self.logger.info("测试环境清理完成")

    def set_base_url(self, url):
        """设置基础URL"""
        self.base_url = url
        self.logger.info(f"设置基础URL: {url}")

    def get_by_type(self, locate_type):
        """
        将定位方式字符串转换为 Selenium By 类型
        """
        locate_type = str(locate_type).lower()
        locate_types = {
            'id': By.ID,
            'name': By.NAME,
            'class': By.CLASS_NAME,
            'tag': By.TAG_NAME,
            'link': By.LINK_TEXT,
            'plink': By.PARTIAL_LINK_TEXT,
            'xpath': By.XPATH,
            'css': By.CSS_SELECTOR,
            # 支持中文定位方式
            'id定位': By.ID,
            'name定位': By.NAME,
            '类名定位': By.CLASS_NAME,
            '标签定位': By.TAG_NAME,
            '链接文本': By.LINK_TEXT,
            '部分链接文本': By.PARTIAL_LINK_TEXT,
            'xpath定位': By.XPATH,
            'css定位': By.CSS_SELECTOR
        }

        if locate_type not in locate_types:
            raise ValueError(f"不支持的定位方式: {locate_type}")

        return locate_types[locate_type]

    def wait_sleep(self, seconds):
        """
        强制等待指定的秒数
        """
        try:
            self.logger.info(f"等待 {seconds} 秒")
            time.sleep(float(seconds))
        except ValueError as e:
            self.logger.error(f"等待时间格式错误: {seconds}")
            raise e

    def verify_text(self, actual_text, expected_text, exact_match=True):
        """
        验证实际文本与预期文本是否匹配
        :param actual_text: 实际文本
        :param expected_text: 预期文本
        :param exact_match: 是否精确匹配，默认为True
        :return: bool
        """
        if not expected_text:  # 如果预期文本为空，则不进行验证
            return True

        actual_text = str(actual_text).strip()
        expected_text = str(expected_text).strip()

        self.logger.info(f"文本验证 - 实际文本: '{actual_text}', 预期文本: '{expected_text}'")

        if exact_match:
            result = actual_text == expected_text
            if not result:
                raise AssertionError(f"文本完全匹配失败: 预期'{expected_text}', 实际'{actual_text}'")
        else:
            result = expected_text in actual_text
            if not result:
                raise AssertionError(f"文本包含匹配失败: 预期文本'{expected_text}'未在实际文本'{actual_text}'中找到")

        return result

    def wait_for_element_presence(self, locate_type, locate_value, timeout=10):
        """
        等待元素在DOM中存在
        """
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.presence_of_element_located((
                    self.get_by_type(locate_type),
                    locate_value
                ))
            )
            return element
        except Exception as e:
            self.logger.error(f"等待元素存在超时: {locate_type}={locate_value}")
            raise e

    def wait_for_element(self, locate_type, locate_value, timeout=10):
        """
        等待元素可见
        """
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.visibility_of_element_located((
                    self.get_by_type(locate_type),
                    locate_value
                ))
            )
            return element
        except Exception as e:
            self.logger.error(f"等待元素可见超时: {locate_type}={locate_value}")
            raise e

    def wait_for_element_clickable(self, locate_type, locate_value, timeout=10):
        """
        等待元素可点击
        """
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.element_to_be_clickable((
                    self.get_by_type(locate_type),
                    locate_value
                ))
            )
            return element
        except Exception as e:
            self.logger.error(f"等待元素可点击超时: {locate_type}={locate_value}")
            raise e

    def wait_for_text_present(self, locate_type, locate_value, text, timeout=10):
        """
        等待文本出现
        """
        try:
            element = WebDriverWait(self.driver, timeout).until(
                EC.text_to_be_present_in_element((
                    self.get_by_type(locate_type),
                    locate_value
                ), text)
            )
            return element
        except Exception as e:
            self.logger.error(f"等待文本出现超时: {locate_type}={locate_value}, 期望文本: {text}")
            raise e

    def calculate_average_step_time(self):
        """计算平均步骤执行时间"""
        durations = [step['执行时长'].total_seconds() for step in self.test_results]
        return sum(durations) / len(durations) if durations else 0

    def get_slowest_step(self):
        """获取执行最慢的步骤信息"""
        if not self.test_results:
            return "无数据"
        slowest = max(self.test_results, key=lambda x: x['执行时长'])
        return f"步骤 {slowest['步骤编号']}: {slowest['执行时长'].total_seconds():.2f}秒"

    def get_step_labels(self):
        """获取步骤标签列表的 JSON 字符串"""
        labels = [f"步骤{step['步骤编号']}" for step in self.test_results]
        return json.dumps(labels)

    def get_step_durations(self):
        """获取步骤执行时间列表的 JSON 字符串"""
        durations = [step['执行时长'].total_seconds() for step in self.test_results]
        return json.dumps(durations)

    def generate_failure_summary(self):
        """生成失败用例汇总"""
        failed_steps = [step for step in self.test_results if step['执行结果'] == 'Fail']
        if not failed_steps:
            return ''

        summary = '''
        <div class="failure-summary">
            <h2>失败用例汇总</h2>
            <ul>
        '''
        for step in failed_steps:
            summary += f'''
                <li>
                    <strong>用例 {step['测试用例编号']} - 步骤 {step['步骤编号']}</strong><br>
                    错误信息: {step['错误信息']}
                </li>
            '''
        summary += '</ul></div>'
        return summary


# 使用示例
if __name__ == "__main__":
    excel_path = "C:\\Users\\leixin\\Desktop\\web_test.xlsx"  # Excel文件路径
    automation = WebUIAutomation(excel_path)
    automation.set_base_url("https://www.baidu.com")  # 设置要测试的网站URL

    try:
        automation.run_tests()
    finally:
        automation.close()
